package com.lwq.project.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.provider.X509CertificateObject;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;
import org.springframework.util.Assert;

public abstract class RSAUtils {
	
	/**
	 * 加密算法RSA
	 */
	public static final String KEY_ALGORITHM = "RSA";
	
	/**
	 * 
	 */
	public static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; 

	/**
	 * 密钥大小 
	 */
	private static final int KEY_SIZE = 4096;

	/**
	 * RSA最大解密密文大小
	 */
	private static final int MAX_DECRYPT_BLOCK = 128;
	
	/**
	 * RSA最大加密明文大小
	 */
	private static final int MAX_ENCRYPT_BLOCK = 117;
	
	/** 安全服务提供者 */
	private static final Provider PROVIDER = new BouncyCastleProvider();
	
	static {
		Security.addProvider(PROVIDER);
	}
	
	/**
	 * 
	 * @param publicKey
	 * @return
	 */
	public static final String getBase64Modulus(RSAPublicKey publicKey) {
		String base64 = Base64.encodeBase64String(publicKey.getModulus().toByteArray());
		return StringUtils.replace(base64, "\r\n", "");

	}
	
	/**
	 * 
	 * @param publicKey
	 * @return
	 */
	public static final String getBase64Exponent(RSAPublicKey publicKey) {
		String base64 = Base64.encodeBase64String(publicKey.getPublicExponent().toByteArray());
		return StringUtils.replace(base64, "\r\n", "");
	}
	
	/**
	 * 生成密钥对
	 * 
	 * @return 密钥对
	 */
	public static KeyPair generateKeyPair() {
		try {
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", PROVIDER);
			keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());
			return keyPairGenerator.generateKeyPair();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 加密
	 * 
	 * @param publicKey
	 *            公钥
	 * @param data
	 *            数据
	 * @return 加密后的数据
	 */
	public static byte[] encrypt(PublicKey publicKey, byte[] data) {
		Assert.notNull(publicKey);
		Assert.notNull(data);
		try {
			Cipher cipher = Cipher.getInstance("RSA", PROVIDER);
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
			return cipher.doFinal(data);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 加密
	 * 
	 * @param publicKey
	 *            公钥
	 * @param text
	 *            字符串
	 * 
	 * @return Base64编码字符串
	 */
	public static String encrypt(PublicKey publicKey, String text) {
		Assert.notNull(publicKey);
		Assert.notNull(text);
		byte[] data = encrypt(publicKey, text.getBytes());
		return data != null ? Base64.encodeBase64String(data) : null;
	}

	/**
	 * 解密
	 * 
	 * @param privateKey
	 *            私钥
	 * @param data
	 *            数据
	 * @return 解密后的数据
	 */
	public static byte[] decrypt(PrivateKey privateKey, byte[] data) {
		Assert.notNull(privateKey);
		Assert.notNull(data);
		try {
			Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", PROVIDER);
			cipher.init(Cipher.DECRYPT_MODE, privateKey);
			return cipher.doFinal(data);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 解密
	 * 
	 * @param privateKey
	 *            私钥
	 * @param text
	 *            Base64编码字符串
	 * @return 解密后的数据
	 */
	public static String decrypt(PrivateKey privateKey, String text) {
		Assert.notNull(privateKey);
		Assert.notNull(text);
		byte[] data = decrypt(privateKey, Base64.decodeBase64(text));
		return data != null ? new String(data) : null;
	}
	
	/**
     * <p>
     * 私钥加密
     * </p>
     * 
     * @param data 源数据
     * @param privateKey 私钥(BASE64编码)
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPrivateKey(byte[] data, String privateKey)
            throws Exception {
        byte[] keyBytes = Base64.decodeBase64(privateKey);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateK);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }
    
    public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey)
            throws Exception {
        byte[] keyBytes = Base64.decodeBase64(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicK);
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }
	
	/**
	 * <p>
	 * 公钥加密
	 * </p>
	 * 
	 * @param data
	 *            源数据
	 * @param publicKey
	 *            公钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] encryptByPublicKey(byte[] data, String publicKey)
			throws Exception {
		byte[] keyBytes = Base64.decodeBase64(publicKey);//BASE64字符串解码转为二进制数据
		//byte[] keyBytes = publicKey.getBytes();//BASE64字符串解码转为二进制数据

		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key publicK = keyFactory.generatePublic(x509KeySpec);
		// 对数据加密
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.ENCRYPT_MODE, publicK);
		int inputLen = data.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;
		byte[] cache;
		int i = 0;
		// 对数据分段加密
		while (inputLen - offSet > 0) {
			if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
				cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
			} else {
				cache = cipher.doFinal(data, offSet, inputLen - offSet);
			}
			out.write(cache, 0, cache.length);
			i++;
			offSet = i * MAX_ENCRYPT_BLOCK;
		}
		byte[] encryptedData = out.toByteArray();
		out.close();
		return encryptedData;
	}
	
	/**
	 * <P>
	 * 私钥解密
	 * </p>
	 * 
	 * @param encryptedData
	 *            已加密数据
	 * @param privateKey
	 *            私钥(BASE64编码)
	 * @return
	 * @throws Exception
	 */
	public static byte[] decryptByPrivateKey(byte[] encryptedData,
			String privateKey) throws Exception {
		byte[] keyBytes = Base64.decodeBase64(privateKey);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(Cipher.DECRYPT_MODE, privateK);
		int inputLen = encryptedData.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;
		byte[] cache;
		int i = 0;
		// 对数据分段解密
		while (inputLen - offSet > 0) {
			if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
				cache = cipher
						.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
			} else {
				cache = cipher
						.doFinal(encryptedData, offSet, inputLen - offSet);
			}
			out.write(cache, 0, cache.length);
			i++;
			offSet = i * MAX_DECRYPT_BLOCK;
		}
		byte[] decryptedData = out.toByteArray();
		out.close();
		return decryptedData;
	}
	
	/**
	 * 将私钥转换成string
	 * @param privateKey
	 * @return
	 */
	public static String toString(RSAPrivateKey privateKey) {
		StringBuilder sb = new StringBuilder();
		sb.append(privateKey.getModulus());
		sb.append("\n");
		sb.append(privateKey.getPrivateExponent());
		return sb.toString();
	}
	
	/**
	 * 
	 * @param c
	 * @param st
	 * @param l
	 * @param o
	 * @param cn
	 * @param kp
	 * @return
	 */
	public static X509Certificate generateX509Certificate(String c, String st, String l, String o, String cn, KeyPair kp) {
		
		try {
	    	X500Name issuer = new X500Name(String.format("C=%s,ST=%s,L=%s,O=%s,CN=%s", c, st, l, o, cn));
	    	X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(issuer, BigInteger.valueOf(System.currentTimeMillis()), new Date(System.currentTimeMillis() - 1000L * 60 * 60), new Date(System.currentTimeMillis() + 1000L * 60 * 60 * 24 * 365 * 10), issuer, kp.getPublic());
	    	
	    	X509CertificateHolder holder = builder.build(new JcaContentSignerBuilder("SHA1WithRSA").setProvider(PROVIDER).build(kp.getPrivate()));
	        X509Certificate cert = new JcaX509CertificateConverter().setProvider(PROVIDER).getCertificate(holder);

	        cert.checkValidity(new Date());

	        cert.verify(kp.getPublic());

	        PKCS12BagAttributeCarrier   bagAttr = (PKCS12BagAttributeCarrier)cert;

	        //
	        // this is actually optional - but if you want to have control
	        // over setting the friendly name this is the way to do it...
	        //
	        bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(cn));

	        return cert;
	     } catch (OperatorCreationException x) {
	    	 throw new RuntimeException(x);
	     } catch (CertificateException x) {
	    	 throw new RuntimeException(x);
	     } catch (NoSuchAlgorithmException x) {
	    	 throw new RuntimeException(x);
	     } catch (InvalidKeyException x) {
	    	 throw new RuntimeException(x);
	     } catch (NoSuchProviderException x) {
	    	 throw new RuntimeException(x);
	     } catch (SignatureException x) {
	    	 throw new RuntimeException(x);
	     }
	}
	
	/**
	 * 
	 * @param c
	 * @param st
	 * @param l
	 * @param o
	 * @param cn
	 * @param kp
	 * @param alias
	 * @param password
	 * @return
	 */
	public static KeyStore generatePfx(String c, String st, String l, String o, String cn, KeyPair kp, String alias, String password) {
		
		try {
	    	X500Name issuer = new X500Name(String.format("C=%s,ST=%s,L=%s,O=%s,CN=%s", c, st, l, o, cn));
	    	X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(issuer, BigInteger.valueOf(System.currentTimeMillis()), new Date(System.currentTimeMillis() - 1000L * 60 * 60), new Date(System.currentTimeMillis() + 1000L * 60 * 60 * 24 * 365 * 10), issuer, kp.getPublic());
	    	
	    	X509CertificateHolder holder = builder.build(new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(PROVIDER).build(kp.getPrivate()));
	        X509Certificate cert = new JcaX509CertificateConverter().setProvider(PROVIDER).getCertificate(holder);

	        cert.checkValidity(new Date());

	        cert.verify(kp.getPublic());

	        PKCS12BagAttributeCarrier   bagAttr = (PKCS12BagAttributeCarrier)cert;

	        //
	        // this is actually optional - but if you want to have control
	        // over setting the friendly name this is the way to do it...
	        //
	        bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(cn));
	        
	        KeyStore store = KeyStore.getInstance("PKCS12");
	        store.load(null, null);  
	        store.setKeyEntry(alias, kp.getPrivate(), password.toCharArray(), new Certificate[] { cert });
	        
	        return store;
	     } catch (OperatorCreationException x) {
	    	 throw new RuntimeException(x);
	     } catch (CertificateException x) {
	    	 throw new RuntimeException(x);
	     } catch (NoSuchAlgorithmException x) {
	    	 throw new RuntimeException(x);
	     } catch (InvalidKeyException x) {
	    	 throw new RuntimeException(x);
	     } catch (NoSuchProviderException x) {
	    	 throw new RuntimeException(x);
	     } catch (SignatureException x) {
	    	 throw new RuntimeException(x);
	     } catch (KeyStoreException x) {
	    	 throw new RuntimeException(x);
	     } catch (IOException x) {
	    	 throw new RuntimeException(x);
	     }
	}
	
	/**
	 * 
	 * @param cert
	 * @param stream
	 */
	public static void write(X509Certificate cert, OutputStream stream) {
		PemWriter pw = null;
	    try {
	    	pw = new PemWriter(new OutputStreamWriter(stream));
	    	pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded()));
	    } catch (CertificateEncodingException x) {
	    	throw new RuntimeException(x);
	    } catch (IOException x) {
	    	throw new RuntimeException(x);
	    } finally {
	        IOUtils.closeQuietly(pw);
	    }
	}
	
	/**
	 * 
	 * @param store
	 * @param password
	 * @param stream
	 */
	public static void write(KeyStore store, String password, OutputStream stream) {
	    try {
	    	store.store(stream, password.toCharArray());
	    } catch (CertificateException x) {
	    	throw new RuntimeException(x);
	    } catch (NoSuchAlgorithmException x) {
	    	throw new RuntimeException(x);
	    } catch (KeyStoreException x) {
	    	throw new RuntimeException(x);
	    } catch (IOException x) {
	    	throw new RuntimeException(x);
	    }
	}
	
	/**
	 * 
	 * @param buffer
	 * @return
	 */
	public static PublicKey loadPublicKeyFromPem(byte[] buffer) {
		PEMReader reader = null;
        try { 
        	reader = new PEMReader(new StringReader(new String(buffer))); 
        	X509CertificateObject co = (X509CertificateObject) reader.readObject(); 
        	return co.getPublicKey();
        } catch (Exception x) { 
        	throw new RuntimeException(x);
        } finally {
        	IOUtils.closeQuietly(reader);
        }
	}
	
	/**
     * Create a key based on the parameters. 
     *  
     * @param modulus 
     * @param publicExponent 
     * @return the key 
     */ 
    public static RSAPublicKey getRSAPublicKey(BigInteger modulus, BigInteger publicExponent) { 
        try { 
            return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, publicExponent)); 
        } catch (GeneralSecurityException e) { 
            throw new RuntimeException(e); 
        } 
    } 
	
	/**
	 * 
	 * @param buffer
	 * @param password
	 * @return
	 */
	public static Object[] loadKeyFromKeyStore(byte[] buffer, String password) {
		ByteArrayInputStream bis = null;
		try {
			bis = new ByteArrayInputStream(buffer);
			KeyStore store = KeyStore.getInstance("PKCS12");
			store.load(bis, password.toCharArray());
			String alias = store.aliases().nextElement();
			return new Object[]{store.getCertificate(alias), store.getKey(alias, password.toCharArray())};
		} catch (KeyStoreException x) {
			throw new RuntimeException(x);
		} catch (CertificateException x) {
			throw new RuntimeException(x);
		} catch (NoSuchAlgorithmException x) {
			throw new RuntimeException(x);
		} catch (IOException x) {
			throw new RuntimeException(x);
		} catch (UnrecoverableKeyException x) {
			throw new RuntimeException(x);
		} finally {
			IOUtils.closeQuietly(bis);
		}
	}
	
	/**
	 * 
	 * @param privateKey
	 * @param data
	 * @param algorithm
	 * @return
	 */
	public static final byte[] sign4b(PrivateKey privateKey, byte[] data, String algorithm) {
		try {
			Signature signature = Signature.getInstance(algorithm);
			signature.initSign(privateKey);
			signature.update(data);
			return signature.sign();
		} catch (NoSuchAlgorithmException x) {
			throw new RuntimeException(x);
		} catch (InvalidKeyException x) {
			throw new RuntimeException(x);
		} catch (SignatureException x) {
			throw new RuntimeException(x);
		}
	}
	
	
	/**
	 * 
	 * @param privateKey
	 * @param data
	 * @param algorithm
	 * @return
	 */
	public static final String sign(PrivateKey privateKey, byte[] data, String algorithm) {
		try {
			return new String(Base64.encodeBase64(RSAUtils.sign4b(privateKey, data, algorithm)), "utf-8");
		} catch (UnsupportedEncodingException x) {
			throw new RuntimeException(x);
		}
	}
	
	/**
	 * 
	 * @param publicKey
	 * @param data
	 * @param signed
	 * @param algorithm
	 * @return
	 */
	public static final boolean validateSign(PublicKey publicKey, byte[] data, byte[] signed, String algorithm) {
		try {
			Signature signature = Signature.getInstance(algorithm);
			signature.initVerify(publicKey);
			signature.update(data);
			
			return signature.verify(signed);
		} catch (NoSuchAlgorithmException x) {
			throw new RuntimeException(x);
		} catch (InvalidKeyException x) {
			throw new RuntimeException(x);
		} catch (SignatureException x) {
			throw new RuntimeException(x);
		}
	}
	
	/**
	 * 
	 * @param args
	 * @throws IOException
	 */
	public static final void main(String[] args) throws IOException {
		String password = "123456";
		KeyPair kp = RSAUtils.generateKeyPair();
		FileOutputStream fs = null;
		FileOutputStream pfs = null;
		try {
			fs = new FileOutputStream("/Users/sihai/Downloads/public.cer");
			RSAUtils.write(RSAUtils.generateX509Certificate("CN", "Zhejiang", "Hanzhou", "opentech.com", "uqiantu.net", kp), fs);
			fs.flush();
			fs.close();
			
			pfs = new FileOutputStream("/Users/sihai/Downloads/private.pfx");
			RSAUtils.write(RSAUtils.generatePfx("CN", "Zhejiang", "Hanzhou", "opentech.com", "uqiantu.net", kp, "qiantu.net", password), password, pfs);
			
			pfs.flush();
			pfs.close();
		} finally {
			IOUtils.closeQuietly(fs);
			IOUtils.closeQuietly(pfs);
		}
	}
}